package net.avcompris.binding.dom.helper;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.avcompris.binding.annotation.XPath;
import net.avcompris.binding.dom.DomBinder;
import net.avcompris.binding.dom.impl.DefaultDomBinder;
import net.avcompris.binding.helper.BinderUtils;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Helpers to quickly load XML contents into Java objects.
 * 
 * @author David Andrianavalontsalama
 */
public abstract class DomBinderUtils extends BinderUtils {

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final String xmlContent,
			final Class<T> clazz) {

		return xmlContentToJava(xmlContent, DefaultDomBinder.getInstance(),
				clazz);
	}

	/**
	 * load a XML {@link Document} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final Document document,
			final Class<T> clazz) {

		return xmlContentToJava(document, DefaultDomBinder.getInstance(), clazz);
	}

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final String xmlContent,
			final DomBinder domBinder, final Class<T> clazz) {

		nonNullArgument(xmlContent, "xmlContent");
		nonNullArgument(clazz, "clazz");
		nonNullArgument(domBinder, "domBinder");

		final Node node;

		try {

			node = loadXmlContent(xmlContent);

		} catch (final ParserConfigurationException e) {
			throw new RuntimeException(e);
		} catch (final SAXException e) {
			throw new RuntimeException(e);
		} catch (final IOException e) {
			throw new RuntimeException(e);
		}

		return DefaultDomBinder.getInstance().bind(node, clazz);
	}

	/**
	 * load a XML {@link Document} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final Document document,
			final DomBinder domBinder, final Class<T> clazz) {

		nonNullArgument(document, "document");
		nonNullArgument(clazz, "clazz");
		nonNullArgument(domBinder, "domBinder");

		final Node node = document.getDocumentElement();

		return DefaultDomBinder.getInstance().bind(node, clazz);
	}

	private static DocumentBuilder getDocumentBuilder()
			throws ParserConfigurationException {

		final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
				.newInstance();

		documentBuilderFactory.setNamespaceAware(true);

		final DocumentBuilder documentBuilder = documentBuilderFactory
				.newDocumentBuilder();

		return documentBuilder;
	}

	private static Element loadXmlContent(final String xmlContent)
			throws ParserConfigurationException, SAXException, IOException {

		nonNullArgument(xmlContent, "xmlContent");

		final DocumentBuilder documentBuilder = getDocumentBuilder();

		final Document document;

		final Reader reader = new StringReader(xmlContent);
		try {

			document = documentBuilder.parse(new InputSource(reader));

		} finally {
			reader.close();
		}

		return document.getDocumentElement();
	}

	private static Element loadXmlContent(final InputStream is)
			throws ParserConfigurationException, SAXException, IOException {

		nonNullArgument(is, "inputStream");

		final DocumentBuilder documentBuilder = getDocumentBuilder();

		final Document document;

		document = documentBuilder.parse(is);

		return document.getDocumentElement();
	}

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final File xmlFile,
			final Class<T> clazz) {

		return xmlContentToJava(xmlFile, DefaultDomBinder.getInstance(), clazz);
	}

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final InputStream is,
			final Class<T> clazz) {

		return xmlContentToJava(is, DefaultDomBinder.getInstance(), clazz);
	}

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final File xmlFile,
			final DomBinder domBinder, final Class<T> clazz) {

		nonNullArgument(xmlFile, "xmlFile");
		nonNullArgument(domBinder, "domBinder");
		nonNullArgument(clazz, "clazz");

		try {

			final InputStream is = new FileInputStream(xmlFile);
			try {

				return xmlContentToJava(is, domBinder, clazz);

			} finally {
				is.close();
			}

		} catch (final IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * load a XML {@link String} into a Java interface annotated with the
	 * {@link XPath}-annotation. This method uses the {@link DefaultDomBinder}
	 * implementation.
	 */
	public static <T> T xmlContentToJava(final InputStream is,
			final DomBinder domBinder, final Class<T> clazz) {

		nonNullArgument(is, "inputStream");
		nonNullArgument(domBinder, "domBinder");
		nonNullArgument(clazz, "clazz");

		final Node node;

		try {

			node = loadXmlContent(is);

		} catch (final ParserConfigurationException e) {
			throw new RuntimeException(e);
		} catch (final SAXException e) {
			throw new RuntimeException(e);
		} catch (final IOException e) {
			throw new RuntimeException(e);
		}

		return domBinder.bind(node, clazz);
	}
}
